package cc.shacocloud.redis.study.util;

import cc.shacocloud.redis.study.util.env.YamlConfigurer;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 定时任务对象
 */
@Slf4j
public abstract class AbstractTimingTask implements Runnable {

    private static final ScheduledExecutorService scheduledExecutor;

    static {
        YamlConfigurer configurer = YamlConfigurer.getDefault();
        Integer corePoolSize = configurer.getIntProperty("scheduled.poolSize", Runtime.getRuntime().availableProcessors() + 1);
        scheduledExecutor = Executors.newScheduledThreadPool(corePoolSize);

        // 挂载到关闭钩子上关闭
        Runtime.getRuntime().addShutdownHook(new Thread(() -> stopTimer(true, Long.MAX_VALUE, TimeUnit.NANOSECONDS)));
    }

    /**
     * 执行线程名称
     */
    private final String name;

    public AbstractTimingTask() {
        this(null);
    }

    /**
     * 线程名称
     *
     * @param name 名称
     */
    public AbstractTimingTask(String name) {
        this.name = name;
    }


    /**
     * 固定延迟执行，即每隔 {@code timeInterval} 毫秒重复执行
     *
     * @param startupDelay 启动间隔 /ms
     * @param timeInterval 执行间隔 /ms
     */
    public void fixedDelay(long startupDelay, long timeInterval) {
        boolean bs = beforeStart();
        if (!bs) return;

        // 定时执行
        scheduledExecutor.scheduleWithFixedDelay(this, startupDelay, timeInterval, TimeUnit.MILLISECONDS);
    }

    /**
     * 在上次执行结束后固定延迟执行，即在上次执行结束后过 {@code period} 毫秒重复执行
     *
     * @param initialDelay 初始延迟 /ms
     * @param period       执行间隔 /ms
     */
    public void fixedRate(long initialDelay, long period) {
        boolean bs = beforeStart();
        if (!bs) return;

        scheduledExecutor.scheduleAtFixedRate(this, initialDelay, period, TimeUnit.MILLISECONDS);
    }

    /**
     * 延迟指定时间后执行一次，即延迟 {@code delay} 毫秒后执行一次
     *
     * @param delay 延迟时间 /ms
     */
    public void delay(long delay) {
        boolean bs = beforeStart();
        if (!bs) return;

        scheduledExecutor.schedule(this, delay, TimeUnit.MILLISECONDS);
    }


    /**
     * 停止定时器
     *
     * @param awaitTermination 是否等待终止，true等待 false不等待
     * @param timeout          最长等待时间
     * @param unit             超时参数的时间单位
     */
    private static void stopTimer(boolean awaitTermination, long timeout, TimeUnit unit) {
        scheduledExecutor.shutdown();

        if (awaitTermination) {
            try {
                boolean termination = scheduledExecutor.awaitTermination(timeout, unit);
                if (!termination) log.warn("等待了{}/ms后任务还未执行结束，强制中断！", unit.toMillis(timeout));
            } catch (InterruptedException e) {
                log.warn("等待执行结束过程中线程被中断！");
            } finally {
                // 如果子线程用到BufferedReader，而BufferedReader的readLine是阻塞的，如果流没有关闭那么会一直读取。
                // 即便是awaitTermination执行超时之后返回到主线程。
                // 但是子线程没有像预计的那样中断退出，因为awaitTermination不会中断线程的。
                // 所以这里使用 shutdownNow 来主动关闭线程
                scheduledExecutor.shutdownNow();
            }
        }

        if (log.isInfoEnabled()) log.info("定时器关闭成功...");
    }

    @Override
    public final void run() {
        if (StrUtil.isNotBlank(name)) Thread.currentThread().setName(name);

        try {
            exec();
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 执行函数
     */
    protected abstract void exec() throws Exception;

    /**
     * 开始之前
     *
     * @return 返回fasle 则不启动
     */
    protected boolean beforeStart() {
        return true;
    }

}
